package com.zhentao.service.impl;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zhentao.common.constants.ScheduleConstants;
import com.zhentao.common.redis.CacheService;
import com.zhentao.mapper.TaskInfoLogsMapper;
import com.zhentao.mapper.TaskInfoMapper;
import com.zhentao.model.schedule.dto.TaskDto;
import com.zhentao.model.schedule.pojo.TaskInfo;
import com.zhentao.model.schedule.pojo.TaskInfoLogs;
import com.zhentao.service.TaskInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
@Transactional
@Slf4j
public class TaskInfoServiceImpl implements TaskInfoService {
    @Autowired
    private TaskInfoMapper taskInfoMapper;

    @Autowired
    private TaskInfoLogsMapper taskInfoLogsMapper;
    @Autowired
    private CacheService cacheService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;


    /**
     * 拉取任务
     *
     * @param type
     * @param priority
     * @return
     */
    @Override
    public TaskDto poll(Integer type, Integer priority) {
        TaskDto task = null;
        try {
            String key = type + "_" + priority;
            //从redis中拉取
            String task_json = cacheService.lRightPop(ScheduleConstants.TOPIC + key);
            if (StringUtils.isNotBlank(task_json)) {
                task = JSON.parseObject(task_json, TaskDto.class);
                //修改数据库信息
                updateDB(task.getTaskId(), ScheduleConstants.EXECUTED);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("poll task Exception");
        }

        //修改数据库信息
        return task;
    }

    @Override
    public long addTask(TaskDto taskDto) {
        //添加数据库： 任务表+任务日志表   任务1-任务表   添加日志表【1 什么时候添加的】

        boolean success = addTaskToDB(taskDto);

        if (success) {
            //把任务添加到redis里面  如 果小于=当前时候，立即执行-》list
            // 如果是未来要执行的--》zset里面
            addTaskToRedis(taskDto);
        }
        return taskDto.getTaskId();
    }

    /**
     * 取消任务
     *
     * @param taskId
     * @return
     */
    @Override
    public boolean canceTask(Long taskId) {
        boolean flag = false;
        //删除任务，更新任务日志
        TaskDto task = updateDB(taskId, ScheduleConstants.CANCELLED);

        //删除redis数据
        if (task != null) {
            removeTaskFromRedis(task);
            flag = true;
        }
        return flag;
    }

    /**
     * 删除redis中的数据
     *
     * @param task
     */
    private void removeTaskFromRedis(TaskDto task) {
        String key = task.getTaskType() + "_" + task.getPriority();
        if (task.getExecuteTime() <= System.currentTimeMillis()) {
            //删除list中坏数据
            cacheService.lRemove(ScheduleConstants.TOPIC + key, 0, JSON.toJSONString(task));
        } else {
            //删除zset中的
            cacheService.zRemove(ScheduleConstants.FUTURE + key, JSON.toJSONString(task));
        }
    }

    /**
     * 删除任务，更新任务的日志
     *
     * @param taskId
     * @param status
     * @return
     */
    private TaskDto updateDB(Long taskId, int status) {
        TaskDto taskDto = null;
        try {
            //删除任务
            taskInfoMapper.deleteById(taskId);
            //更新日志
            TaskInfoLogs taskInfoLogs = taskInfoLogsMapper.selectById(taskId);
            taskInfoLogs.setStatus(status);
            taskInfoLogsMapper.updateById(taskInfoLogs);

            taskDto = new TaskDto();
            //日志中
            BeanUtils.copyProperties(taskInfoLogs, taskDto);
            taskDto.setExecuteTime(taskInfoLogs.getExecuteTime().getTime());

        } catch (Exception e) {
            log.error("task cance exception taskeId={}", taskId);
        }
        return taskDto;
    }

    /**
     * 把任务添加到redis
     *
     * @param taskDto
     */
    public void addTaskToRedis(TaskDto taskDto) {
     /*   把任务添加到redis里面
          如果任务的执行时间小于=当前时候，立即执行-》list 消息队列里面 --》然后再去消费 写一个定时任务 1s一次，<=当前时间的，立马执行
        如果是未来要执行的--》zset里面   ？ 是所有的数据都放到zset里面吗 ？
                未来5分钟的数据放到zSet里面，也写一个定时任务 1分钟扫描一次，如果zset里面得任务在1分钟之内，放到list里面

        大于5分钟数据怎么办，在数据库里面； 然后写一个定时任务去扫描数据库，5分钟扫描一次，如果数据库里面得任务在5分钟之内，放到zSet里面
    */
        //判断时间是在在5分钟之内，获取到5分钟之后的毫秒值
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, 5);
        Long nextTime = calendar.getTimeInMillis();


        //定义key  1.发布文章  "submit:current:1     2.审核  submit:current:2
        String key = taskDto.getTaskType() + "_" + taskDto.getPriority();

        if (taskDto.getExecuteTime() <= System.currentTimeMillis()) {
            //如果小于=当前时间  -->list里面
            cacheService.lLeftPush(ScheduleConstants.TOPIC + key, JSON.toJSONString(taskDto));
            //设置taskId
            taskDto.setTaskId(taskDto.getTaskId());
            log.info("放到list{},{}", taskDto, new Date().toLocaleString());

        } else if (taskDto.getExecuteTime() <= nextTime) {
            //5分钟之内的数据  -->zset里面
            cacheService.zAdd(ScheduleConstants.FUTURE + key, JSON.toJSONString(taskDto), taskDto.getExecuteTime());
            log.info("放到zSet{},{}", taskDto, new Date().toLocaleString());
        }


    }


    public boolean addTaskToDB(TaskDto dto) {
        boolean flag = false;
        try {
            //任务表
            TaskInfo taskInfo = new TaskInfo();
            BeanUtils.copyProperties(dto, taskInfo);
            //设置执行的时间
            taskInfo.setExecuteTime(new Date(dto.getExecuteTime()));
            taskInfoMapper.insert(taskInfo);
            //设置taskid
            dto.setTaskId(taskInfo.getTaskId());
            //任务日志表
            TaskInfoLogs taskInfoLogs = new TaskInfoLogs();
            BeanUtils.copyProperties(taskInfo, taskInfoLogs);
            //乐观锁的版本号
            taskInfoLogs.setVersion(1);
            taskInfoLogs.setStatus(ScheduleConstants.SCHEDULED);
            taskInfoLogsMapper.insert(taskInfoLogs);
            flag = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return flag;

    }

    //zet--- list里面
    //未来数据定时刷新
    @Scheduled(cron = "0 */1 * * * ?")
    public void refresh() {
        String token = cacheService.tryLock("FUTRUE_TASK_SYNC", 1000 * 30);

        if (StringUtils.isNotBlank(token)) {
            log.info("未来数据定时刷新----定时任务");
            if (zSetlock()) {
                log.info("把zset里面得数据刷新到list里面，{}", new Date().toLocaleString());

                //1.获取到当前要消费的数据

                Set<String> futureKeys = cacheService.scan(ScheduleConstants.FUTURE + "*");
                //按照分支查询苏荷条件
                //2.把数据放到list里面
                for (String futureKey : futureKeys) {
                    //获取当前数据的key
                    String topicKey = ScheduleConstants.TOPIC + futureKey.split(ScheduleConstants.FUTURE)[1];
                    Set<String> tasks = cacheService.zRangeByScore(futureKey, 0, System.currentTimeMillis());
                    //同步数据
                    if (!tasks.isEmpty()) {
                        cacheService.refreshWithPipeline(futureKey, topicKey, tasks);
                        log.info("成功的将" + futureKey + "刷新到了" + topicKey);
                    }
                }

            }
        }

    }

    /**
     * 数据库任务定时同步到redis
     */
    @PostConstruct//表明该方法是一个初始化方法
    @Scheduled(cron = "0 */5 * * * ?")
    public void reloadDate() {
        //清理缓存中的数据 list缓存 zset
        clearCache();
        //查询符合条件的任务 小于为了5分钟的数据
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, 5);
        List<TaskInfo> taskInfos = taskInfoMapper.selectList
                (Wrappers.<TaskInfo>lambdaQuery().
                        lt(TaskInfo::getExecuteTime, calendar.getTime()));

        //把任务添加到redis
        if (taskInfos != null && taskInfos.size() > 0) {
            for (TaskInfo taskInfo : taskInfos) {
                TaskDto task = new TaskDto();
                BeanUtils.copyProperties(taskInfo, task);
                task.setExecuteTime(taskInfo.getExecuteTime().getTime());
                addTaskToRedis(task);
            }
        }

        log.info("数据库的任务已经同步到了redis");
    }

    public void clearCache() {
        Set<String> topicKeys = cacheService.scan(ScheduleConstants.TOPIC + "*");
        Set<String> futureKeys = cacheService.scan(ScheduleConstants.FUTURE + "*");
        cacheService.delete(topicKeys);
        cacheService.delete(futureKeys);
    }

    //加锁
    public Boolean zSetlock() {

        //setNx key
        String key = "Lock:zSetName";
        //值
        String value = UUID.randomUUID().toString();

        try {
            //使用setNx方法 如果key不存在返回true  如果key存在返回false
            //为了防止永远进不来，给他设置一个过期时间
            Boolean b = redisTemplate.opsForValue().setIfAbsent(key, value, 5000, TimeUnit.MILLISECONDS);
            if (b) {  //加锁成功
                return true;
            } else {
                return false;
            }
        } finally {
            //什么时候释放锁   不管出不出异常都要释放锁
            //怎么释放锁   确保谁加的锁谁去释放锁
            if (value.equals(redisTemplate.opsForValue().get(key))) {
                redisTemplate.delete(key);
            }
        }


    }


}

